//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#include "NjExample01.h"

#if NJ_PLATFORM_OS_WIN
#	include "NjEventsSystemWin.h"

	//----------------------------------------------------------------------------
	class NjEventsSystemWin::Static
	{
	public:
		static NjUint const DEFAULT_SPIN_COUNT = 4096;

		NJ_DISABLE_INSTANCE(Static);
	};
	//----------------------------------------------------------------------------
	class NjEventsSystemWin::EventImpl : public NjAtomicStack::Node
	{
	public:
		EventImpl(NjBool bSet);
		~EventImpl();

		TryResult TryWait(TimeMs uTimeMs);
		NjBool Set();
		NjBool Unset();

		NjBool Disable();
		void Enable(NjBool bSet);

	private:
		static DWORD ToWinTime(TimeMs uTimeMs);

		NjAtomic<NjUint> m_kFlags;

		static const NjUint FLAG_ENABLED = 0x00000001;
		static const NjUint FLAG_SET = 0x00000002;
		static const NjUint FLAG_BUSY_COUNTER = 0x00000004;

#if NJ_PLATFORM_32_BIT
		static const NjUint FLAG_BUSY_MASK = 0x0000FFFC;
#else // NJ_PLATFORM_32_BIT
		static const NjUint FLAG_BUSY_MASK = 0x00000001FFFFFFFCULL;
#endif // NJ_PLATFORM_32_BIT

		HANDLE m_kHandle;

		static const NjUint COUNTER_ONE = FLAG_BUSY_MASK+FLAG_BUSY_COUNTER;

		NJ_DISABLE_DEFAULT_CONSTRUCTOR(EventImpl);
		NJ_DISABLE_COPY(EventImpl);
	};
	//----------------------------------------------------------------------------
	NjEventsSystemWin::NjEventsSystemWin()
#if NJ_ASSERT_ENABLED
	:
		m_kTotEvents(0)
#endif // NJ_ASSERT_ENABLED
	{
		SYSTEM_INFO kSystemInfo;
		GetSystemInfo(&kSystemInfo);

		if (kSystemInfo.dwNumberOfProcessors<=1)
			m_uSpinCount=1;
		else
			m_uSpinCount=Static::DEFAULT_SPIN_COUNT;
	}
	//----------------------------------------------------------------------------
	NjEventsSystemWin::~NjEventsSystemWin()
	{
		FlushCache();
		NJ_ASSERT(m_kTotEvents.Get()==0);
	}
	//----------------------------------------------------------------------------
	void NjEventsSystemWin::FlushCache()
	{
		NjAtomicStack * pFreeEvents(m_aFreeEvents);
		NjAtomicStack const * const pFreeEventsEnd(pFreeEvents+NJ_LENGTH_OF(m_aFreeEvents));

		while (pFreeEvents<pFreeEventsEnd)
		{
			NjAtomicStack::FlushNode * pFlushNode(pFreeEvents->Flush());

			while (pFlushNode!=NJ_NULL)
			{
				EventImpl * const pToDelete(static_cast<EventImpl *>(pFlushNode->GetNode()));
				pFlushNode=pFlushNode->GetNext();

				NjDelete(pToDelete);
				NJ_ASSERT(m_kTotEvents.PostDecrement()>0);
			}

			++pFreeEvents;
		}
	}
	//----------------------------------------------------------------------------
	NjEventsSystemWin::Event * NjEventsSystemWin::CreateEvent(NjBool bSet)
	{
		NJ_ASSERT(IsImplementationSet());

		NjAtomicStack::Node * pNode;

		if (bSet)
			pNode=m_aFreeEvents[0].Pop();
		else
			pNode=m_aFreeEvents[1].Pop();

		if (pNode)
		{
			EventImpl * const pEventImpl(static_cast<EventImpl *>(pNode));
			pEventImpl->Enable(bSet);
			return reinterpret_cast<Event *>(pEventImpl);
		}

		if (bSet)
			pNode=m_aFreeEvents[1].Pop();
		else
			pNode=m_aFreeEvents[0].Pop();

		if (pNode)
		{
			EventImpl * const pEventImpl(static_cast<EventImpl *>(pNode));
			pEventImpl->Enable(!bSet);

			if (bSet)
				pEventImpl->Set();
			else
				pEventImpl->Unset();

			return reinterpret_cast<Event *>(pEventImpl);
		}

		NJ_ASSERT(m_kTotEvents.PreIncrement()>0);
		return reinterpret_cast<Event *>(NjNew(EventImpl(bSet)));
	}
	//----------------------------------------------------------------------------
	NjEventsSystemWin::TryResult NjEventsSystemWin::TryWaitEvent(Event * pEvent, TimeMs uTimeMs)
	{
		NJ_ASSERT(IsImplementationSet());

		EventImpl * const pEventImpl(reinterpret_cast<EventImpl *>(pEvent));

		if (pEventImpl==NJ_NULL)
			return NjEventsSystem::TRY_FAILURE;

		return pEventImpl->TryWait(uTimeMs);
	}
	//----------------------------------------------------------------------------
	NjBool NjEventsSystemWin::SetEvent(Event * pEvent)
	{
		NJ_ASSERT(IsImplementationSet());

		EventImpl * const pEventImpl(reinterpret_cast<EventImpl *>(pEvent));

		if (pEventImpl==NJ_NULL)
			return NJ_FALSE;

		return pEventImpl->Set();
	}
	//----------------------------------------------------------------------------
	NjBool NjEventsSystemWin::UnsetEvent(Event * pEvent)
	{
		NJ_ASSERT(IsImplementationSet());

		EventImpl * const pEventImpl(reinterpret_cast<EventImpl *>(pEvent));

		if (pEventImpl==NJ_NULL)
			return NJ_FALSE;

		return pEventImpl->Unset();
	}
	//----------------------------------------------------------------------------
	NjBool NjEventsSystemWin::DestroyEvent(Event * pEvent)
	{
		NJ_ASSERT(IsImplementationSet());

		EventImpl * const pEventImpl(reinterpret_cast<EventImpl *>(pEvent));

		if (pEventImpl==NJ_NULL)
			return NJ_FALSE;

		if (pEventImpl->Disable())
			m_aFreeEvents[0].Push(pEventImpl);
		else
			m_aFreeEvents[1].Push(pEventImpl);

		return NJ_TRUE;
	}
	//----------------------------------------------------------------------------
	NjBool NjEventsSystemWin::GetCurrentThreadDebugId(NjUint & uDebugId)
	{
		NJ_ASSERT(IsImplementationSet());

		uDebugId=NjUint(::GetCurrentThreadId());
		return NJ_TRUE;
	}
	//----------------------------------------------------------------------------
	NjBool NjEventsSystemWin::GetTimeMs(TimeMs & uTimeMs)
	{
		uTimeMs=TimeMs(::GetTickCount());
		return NJ_TRUE;
	}
	//----------------------------------------------------------------------------
	NjBool NjEventsSystemWin::GetSpinCount(NjUint & uSpinCount)
	{
		uSpinCount=m_uSpinCount;
		return NJ_TRUE;
	}
	//----------------------------------------------------------------------------
	inline NjEventsSystemWin::EventImpl::EventImpl(NjBool bSet)
	{
		BOOL bInitialState;
		NjUint uFlags;

		if (bSet)
		{
			bInitialState=TRUE;
			uFlags=FLAG_ENABLED|FLAG_SET;
		}
		else
		{
			bInitialState=FALSE;
			uFlags=FLAG_ENABLED;
		}

		m_kHandle=::CreateEventA(NJ_NULL, TRUE, bInitialState, NJ_NULL);
		NJ_ASSERT(m_kHandle!=NJ_NULL);

		m_kFlags.Set(uFlags);
	}
	//----------------------------------------------------------------------------
	inline NjEventsSystemWin::EventImpl::~EventImpl()
	{
		NJ_ASSERT((m_kFlags.Get()&FLAG_ENABLED)==0);
	}
	//----------------------------------------------------------------------------
	inline NjEventsSystemWin::TryResult NjEventsSystemWin::EventImpl::TryWait(TimeMs uTimeMs)
	{
		if (m_kHandle!=NJ_NULL && (m_kFlags.Get()&FLAG_ENABLED)!=0)
		{
			DWORD const uResult(::WaitForSingleObject(m_kHandle, ToWinTime(uTimeMs)));

			if (uResult==WAIT_OBJECT_0)
				return NjEventsSystem::TRY_SUCCESS;

			if (uResult==WAIT_TIMEOUT)
				return NjEventsSystem::TRY_TIMEOUT;
		}

		NJ_BREAK();
		return NjEventsSystem::TRY_FAILURE;
	}
	//----------------------------------------------------------------------------
	NjBool NjEventsSystemWin::EventImpl::Set()
	{
		NJ_ASSERT(m_kHandle!=NJ_NULL);
		NjUint uFlags(m_kFlags.PreAdd(FLAG_BUSY_COUNTER));

		for(;;)
		{
			NjBool const bDisabled((uFlags&FLAG_ENABLED)==0);
			NJ_ASSERT((uFlags&FLAG_BUSY_MASK)!=0 && (!bDisabled));
			
			if (bDisabled || (uFlags&FLAG_SET)!=0)
			{
#if NJ_ASSERT_ENABLED
				uFlags=
#endif // NJ_ASSERT_ENABLED

				m_kFlags.PostSub(FLAG_BUSY_COUNTER);

				NJ_ASSERT((uFlags&FLAG_BUSY_MASK)!=0);
				return !bDisabled;
			}

#if NJ_ASSERT_ENABLED
			NjBool const bResult=0!=
#endif // NJ_ASSERT_ENABLED

			::SetEvent(m_kHandle);
			
			NJ_ASSERT(bResult);

			NjUint const uNewFlags(uFlags+COUNTER_ONE-FLAG_BUSY_COUNTER+FLAG_SET);
			NjUint const uOldFlags(m_kFlags.PostCompareExchange(uFlags, uNewFlags));

			if (uOldFlags==uFlags)
				return NJ_TRUE;

			uFlags=uOldFlags;
		}
	}
	//----------------------------------------------------------------------------
	NjBool NjEventsSystemWin::EventImpl::Unset()
	{
		NJ_ASSERT(m_kHandle!=NJ_NULL);
		NjUint uFlags(m_kFlags.PreAdd(FLAG_BUSY_COUNTER));

		for(;;)
		{
			NjBool const bDisabled((uFlags&FLAG_ENABLED)==0);
			NJ_ASSERT((uFlags&FLAG_BUSY_MASK)!=0 && (!bDisabled));
			
			if (bDisabled || (uFlags&FLAG_SET)==0)
			{
#if NJ_ASSERT_ENABLED
				uFlags=
#endif // NJ_ASSERT_ENABLED

				m_kFlags.PostSub(FLAG_BUSY_COUNTER);

				NJ_ASSERT((uFlags&FLAG_BUSY_MASK)!=0);
				return !bDisabled;
			}

#if NJ_ASSERT_ENABLED
			NjBool const bResult=0!=
#endif // NJ_ASSERT_ENABLED

			::ResetEvent(m_kHandle);
			
			NJ_ASSERT(bResult);

			NjUint const uNewFlags(uFlags+COUNTER_ONE-FLAG_BUSY_COUNTER-FLAG_SET);
			NjUint const uOldFlags(m_kFlags.PostCompareExchange(uFlags, uNewFlags));

			if (uOldFlags==uFlags)
				return NJ_TRUE;

			uFlags=uOldFlags;
		}
	}
	//----------------------------------------------------------------------------
	inline NjBool NjEventsSystemWin::EventImpl::Disable()
	{
		NJ_ASSERT(m_kHandle!=NJ_NULL);
		NjUint uFlags(m_kFlags.PostAnd(~FLAG_ENABLED));
		NJ_ASSERT((uFlags&FLAG_ENABLED)!=0);
		
		while ((uFlags&FLAG_BUSY_MASK)!=0)
			uFlags=m_kFlags.Get();

		NjBool const bSet((uFlags&FLAG_SET)!=0);

#if NJ_ASSERT_ENABLED
		DWORD const uResult(::WaitForSingleObject(m_kHandle, 0));

		NJ_ASSERT((uFlags&FLAG_ENABLED)!=0);

		if (bSet)
			NJ_ASSERT(uResult==WAIT_OBJECT_0);
		else
			NJ_ASSERT(uResult==WAIT_TIMEOUT);
#endif // NJ_ASSERT_ENABLED

		return bSet;
	}
	//----------------------------------------------------------------------------
	inline void NjEventsSystemWin::EventImpl::Enable(NjBool bSet)
	{
		NJ_ASSERT(m_kHandle!=NJ_NULL);

#if NJ_ASSERT_ENABLED
		NjUint const uFlags=
#else // NJ_ASSERT_ENABLED
		NJ_UNUSED_VAR(bSet);
#endif // NJ_ASSERT_ENABLED
			
		m_kFlags.PostOr(FLAG_ENABLED);

		NJ_ASSERT((uFlags&FLAG_ENABLED)==0);
		NJ_ASSERT(((uFlags&FLAG_SET)!=0)==bSet);
	}
	//----------------------------------------------------------------------------
	inline DWORD NjEventsSystemWin::EventImpl::ToWinTime(TimeMs uTimeMs)
	{
		if (uTimeMs==NjEventsSystem::WAIT_FOREVER)
			return INFINITE;
		else
			return DWORD(uTimeMs);
	}
	//----------------------------------------------------------------------------

#endif // NJ_PLATFORM_OS_WIN
